package com.gitee.qdbp.jdbc.plugins;

import java.lang.reflect.Array;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import com.gitee.qdbp.able.convert.BeanToMapConverter;
import com.gitee.qdbp.able.convert.MapToBeanConverter;
import com.gitee.qdbp.able.convert.ObjectTypeConverter;
import com.gitee.qdbp.able.jdbc.base.OrderByCondition;
import com.gitee.qdbp.able.jdbc.base.UpdateCondition;
import com.gitee.qdbp.able.jdbc.base.WhereCondition;
import com.gitee.qdbp.able.jdbc.condition.TableJoin;
import com.gitee.qdbp.jdbc.model.AllFieldColumn;
import com.gitee.qdbp.jdbc.model.DbType;
import com.gitee.qdbp.jdbc.model.DbVersion;
import com.gitee.qdbp.jdbc.model.MainDbType;
import com.gitee.qdbp.jdbc.model.OmitStrategy;
import com.gitee.qdbp.jdbc.model.SimpleFieldColumn;
import com.gitee.qdbp.jdbc.model.TablesFieldColumn;
import com.gitee.qdbp.jdbc.model.TypedDbVariable;
import com.gitee.qdbp.jdbc.operator.DbBaseOperator;
import com.gitee.qdbp.jdbc.result.RowToBeanMapper;
import com.gitee.qdbp.jdbc.result.RowToBeanMapper.FactoryOfTable;
import com.gitee.qdbp.jdbc.result.RowToBeanMapper.FactoryOfTables;
import com.gitee.qdbp.jdbc.result.RowToMapMapper;
import com.gitee.qdbp.jdbc.utils.DbTypes;
import com.gitee.qdbp.jdbc.utils.ParseTools;
import com.gitee.qdbp.staticize.tags.base.Taglib;
import com.gitee.qdbp.tools.property.PropertyContainer;
import com.gitee.qdbp.tools.utils.ConvertTools;
import com.gitee.qdbp.tools.utils.VerifyTools;
import org.springframework.jdbc.core.SqlParameterValue;

/**
 * 插件帮助类
 *
 * @author zhaohuihua
 * @version 20210626
 */
public class DbPluginHelper {

    public interface Aware {

        void setPlugins(DbPluginHelper plugins);
    }

    private final DbPluginContainer plugins;
    private final ParseTools parseTools;

    DbPluginHelper(DbPluginContainer plugins) {
        this.plugins = plugins;
        this.parseTools = new ParseTools(this);
    }

    /** 解析工具类 **/
    public ParseTools parseTools() {
        return parseTools;
    }

    /** 获取配置项 **/
    public PropertyContainer getSqlConfig() {
        return this.plugins.getSqlConfig();
    }

    /** 通过配置项检查是否支持某选项 **/
    public boolean configSupports(String configKey, DbVersion version) {
        return configSupports(configKey, null, version);
    }

    /** 通过配置项检查是否支持某选项 **/
    public boolean configSupports(String configKey, String defaultValue, DbVersion version) {
        String value = plugins.getSqlConfig().getStringUseDefValue(configKey, defaultValue);
        return version.matchesWith(value);
    }

    /**
     * 将变量转换为数据库字段值<br>
     * 基本对象不做转换, 直接返回, 其他对象转换为基本对象后返回<br>
     * 基本对象是指Boolean/Character/Date/Number/String
     * 
     * @param variable 变量
     * @param dialect 数据库方言
     * @return 转换后的字段值对象
     */
    public Object variableToDbValue(Object variable, SqlDialect dialect) {
        if (variable instanceof Collection) {
            Collection<?> list = (Collection<?>) variable;
            List<Object> result = new ArrayList<>();
            for (Object item : list) {
                result.add(variableToDbValue(item, dialect));
            }
            return result;
        } else if (variable != null && variable.getClass().isArray()) {
            Object[] array = (Object[]) variable;
            Object[] result = (Object[]) Array.newInstance(variable.getClass().getComponentType(), array.length);
            for (int i = 0; i < array.length; i++) {
                result[i] = variableToDbValue(array[i], dialect);
            }
            return result;
        } else {
            if (variable instanceof SqlParameterValue) {
                return variable;
            }
            VariableToDbValueConverter converter = plugins.getToDbValueConverter();
            Object result = converter.convert(variable);
            if (result instanceof TypedDbVariable) { // 转换结果指定了类型
                TypedDbVariable temp = (TypedDbVariable) result;
                return new SqlParameterValue(temp.getJdbcType(), temp.getValue());
            } else if (variable instanceof TypedDbVariable) { // 入参指定了类型
                TypedDbVariable typed = (TypedDbVariable) variable;
                return new SqlParameterValue(typed.getJdbcType(), result);
            } else if (result instanceof Character) { // Character类型不能自动识别
                // @see org.springframework.jdbc.core.StatementCreatorUtils.setValue
                // 调用的是PreparedStatement.setObject(int index, Object x)
                // 而不是PreparedStatement.setObject(int index, Object x, int Types.xxx)
                return new SqlParameterValue(Types.VARCHAR, variable);
            } else {
                return result;
            }
        }
    }

    /**
     * 将变量转换为字符串, 用于拼接SQL<br>
     * 如字符串会返回单引号括起来的'stringValue', Boolean会返回1或0<br>
     * 日期: MYSQL: '2019-06-01 12:34:56.789'<br>
     * ORACLE: TO_TIMESTAMP('2019-06-01 12:34:56.789', 'YYYY-MM-DD HH24:MI:SS.FF')
     * 
     * @param variable 变量
     * @param dialect 数据库方言
     * @return 转换后的字符串
     */
    public String variableToString(Object variable, SqlDialect dialect) {
        if (variable instanceof Collection) {
            Collection<?> list = (Collection<?>) variable;
            List<String> result = new ArrayList<>();
            for (Object item : list) {
                result.add(variableToString(item, dialect));
            }
            return ConvertTools.joinToString(result, ',', false);
        } else if (variable != null && variable.getClass().isArray()) {
            Object[] array = (Object[]) variable;
            String[] result = new String[array.length];
            for (int i = 0; i < array.length; i++) {
                result[i] = variableToString(array[i], dialect);
            }
            return ConvertTools.joinToString(result, ',', false);
        } else {
            VariableToDbValueConverter converter = plugins.getToDbValueConverter();
            Object result = converter.convert(variable);
            if (result instanceof TypedDbVariable) {
                TypedDbVariable temp = (TypedDbVariable) result;
                result = temp.getValue();
            } else if (result instanceof SqlParameterValue) {
                SqlParameterValue temp = (SqlParameterValue) result;
                result = temp.getValue();
            }
            if (result == null) {
                return "NULL";
            } else if (result instanceof Number) {
                return result.toString();
            } else if (result instanceof CharSequence) {
                return dialect.variableToString(result.toString());
            } else if (result instanceof Boolean) {
                return dialect.variableToString((Boolean) result);
            } else if (result instanceof Date) {
                return dialect.variableToString((Date) result);
            } else if (result instanceof Character) {
                return dialect.variableToString(result.toString());
            } else {
                return dialect.variableToString(result.toString());
            }
        }
    }

    /**
     * 将代码中写的数据类型转换为java.sql.Types中的数据类型
     * 
     * @param dataType 代码中的数据类型<br>
     *            1.来自SQL模板中, 如#{createTime,jdbcType=TIMESTAMP}<br>
     *            2.来自实体类上的注解, 如@Column(columnDefinition="TIMESTAMP NOT NULL")
     * @return java.sql.Types中的数据类型
     * @see java.sql.Types
     * @see JdbcDataTypeResolver
     */
    public Integer parseJdbcDataType(String dataType) {
        JdbcDataTypeResolver resolver = plugins.getJdbcDataTypeResolver();
        return resolver.parseDataType(dataType);
    }

    /**
     * 获取Jdbc数据类型转换类
     * 
     * @return JdbcDataTypeResolver
     */
    public JdbcDataTypeResolver getJdbcDataTypeResolver() {
        return plugins.getJdbcDataTypeResolver();
    }

    /**
     * 可用的数据库类型<br>
     * 除MainDbType以外, 可通过plugins.addDbType()注册其他数据库类型
     * 
     * @return 数据库类型列表
     */
    public List<DbType> getAvailableDbTypes() {
        return plugins.getAvailableDbTypes();
    }

    /**
     * 判断数据库类型是否有效
     * 
     * @param dbType 数据库类型字符串
     * @return 是否有效
     */
    public boolean isAvailableDbType(String dbType) {
        if (dbType == null || dbType.length() == 0) {
            return false;
        }
        List<DbType> dbTypes = getAvailableDbTypes();
        for (DbType item : dbTypes) {
            if (DbTypes.equals(item, MainDbType.Unknown)) {
                continue;
            }
            if (item.name().equalsIgnoreCase(dbType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 解析数据库类型
     * 
     * @param dbType 数据库类型字符串
     * @return 数据库类型对象
     */
    public DbType parseDbType(String dbType) {
        if (dbType == null || dbType.length() == 0) {
            return null;
        }

        List<DbType> dbTypes = getAvailableDbTypes();
        for (DbType item : dbTypes) {
            if (DbTypes.equals(item, MainDbType.Unknown)) {
                continue;
            }
            if (item.name().equalsIgnoreCase(dbType)) {
                return item;
            }
        }
        return MainDbType.Unknown;
    }

    /**
     * 新建一个SQL标签库<br>
     * 默认的标签库为classpath:settings/qdbc/qdbc.taglib.txt<br>
     * 可通过plugins.setSqlTaglibPath()修改
     * 
     * @return SQL标签库
     */
    public Taglib newSqlTaglib() {
        return plugins.getSqlTaglibCreator().create();
    }

    /**
     * 转换原始关键字, 如sysdate/CURRENT_TIMESTAMP之前的互转
     * 
     * @param rawValue 原生值, 如sysdate
     * @param dialect 数据库方言
     * @return 转换后的值, 如mysql的CURRENT_TIMESTAMP, SqlServer的GETDATE()
     * @see RawValueConverter
     */
    public String resolveRawValue(String rawValue, SqlDialect dialect) {
        RawValueConverter converter = plugins.getRawValueConverter();
        return converter.convert(rawValue, dialect);
    }

    /** 获取对象类型转换处理类 **/
    public ObjectTypeConverter getObjectTypeConverter() {
        return plugins.getObjectTypeConverter();
    }

    /** 获取Row到Map的转换处理类 **/
    public RowToMapMapper getRowToMapConverter() {
        return plugins.getRowToMapConverter();
    }

    /** 获取Row到Bean的转换处理类 (单表) **/
    public <T> RowToBeanMapper<T> getRowToBeanMapper(Class<T> clazz) {
        FactoryOfTable factory = plugins.getTableRowToBeanFactory();
        return factory.getRowToBeanMapper(clazz);
    }

    /** 获取Row到Bean的转换处理类 (多表关联) **/
    public <T> RowToBeanMapper<T> getRowToBeanMapper(TableJoin tables, Class<T> clazz) {
        FactoryOfTables factory = plugins.getTablesRowToBeanFactory();
        return factory.getRowToBeanMapper(tables, clazz);
    }

    /** 获取Map到JavaBean的转换处理类 **/
    public MapToBeanConverter getMapToBeanConverter() {
        return plugins.getMapToBeanConverter();
    }

    /** JavaBean到Map的转换处理类 **/
    public BeanToMapConverter getBeanToMapConverter() {
        return plugins.getBeanToMapConverter();
    }

    /** 获取JavaBean到数据库条件的转换处理类 **/
    public DbConditionConverter getDbConditionConverter() {
        return plugins.getDbConditionConverter();
    }

    /** 实体类逻辑删除数据状态填充策略 **/
    public EntityFieldFillStrategy getEntityFieldFillStrategy() {
        return plugins.getEntityFieldFillStrategy();
    }

    /** 实体类逻辑删除数据状态填充策略 **/
    public EntityDataStateFillStrategy<?> getEntityDataStateFillStrategy() {
        return plugins.getEntityDataStateFillStrategy();
    }

    /** 根据DbType生成SqlDialect **/
    public SqlDialect buildSqlDialect(DbType dbType) {
        return buildSqlDialect(new DbVersion(dbType));
    }

    /** 根据DbVersion生成SqlDialect **/
    public SqlDialect buildSqlDialect(DbVersion version) {
        SqlDialect.Creator creator = plugins.getSqlDialectCreator();
        return creator.create(version);
    }

    /** 获取自定义WhereSqlBuilder **/
    public <T extends WhereCondition, B extends WhereSqlBuilder<T>> B getWhereSqlBuilder(Class<T> type) {
        return plugins.getWhereSqlBuilder(type);
    }

    /** 获取自定义WhereSqlBuilder **/
    public <T extends WhereCondition, B extends WhereSqlBuilder<T>> B getWhereSqlBuilder(T condition) {
        @SuppressWarnings("unchecked")
        Class<T> type = (Class<T>) condition.getClass();
        return plugins.getWhereSqlBuilder(type);
    }

    /** 获取自定义UpdateSqlBuilder **/
    public <T extends UpdateCondition, B extends UpdateSqlBuilder<T>> B getUpdateSqlBuilder(Class<T> type) {
        return plugins.getUpdateSqlBuilder(type);
    }

    /** 获取自定义UpdateSqlBuilder **/
    public <T extends UpdateCondition, B extends UpdateSqlBuilder<T>> B getUpdateSqlBuilder(T condition) {
        @SuppressWarnings("unchecked")
        Class<T> type = (Class<T>) condition.getClass();
        return plugins.getUpdateSqlBuilder(type);
    }

    /** 获取自定义UpdateSqlBuilder **/
    public <T extends OrderByCondition, B extends OrderBySqlBuilder<T>> B getOrderBySqlBuilder(Class<T> type) {
        return plugins.getOrderBySqlBuilder(type);
    }

    /** 获取自定义UpdateSqlBuilder **/
    public <T extends OrderByCondition, B extends OrderBySqlBuilder<T>> B getOrderBySqlBuilder(T condition) {
        @SuppressWarnings("unchecked")
        Class<T> type = (Class<T>) condition.getClass();
        return plugins.getOrderBySqlBuilder(type);
    }

    /**
     * 根据运算符获取WhereOperator处理类
     * 
     * @param operatorType 运算符
     * @return WhereOperator处理类
     */
    public DbBaseOperator getWhereOperator(String operatorType) {
        DbOperatorContainer container = plugins.getOperatorContainer();
        return container == null ? null : container.getWhereOperator(operatorType);
    }

    /**
     * 根据运算符获取UpdateOperator处理类
     * 
     * @param operatorType 运算符
     * @return UpdateOperator处理类
     */
    public DbBaseOperator getUpdateOperator(String operatorType) {
        DbOperatorContainer container = plugins.getOperatorContainer();
        return container == null ? null : container.getUpdateOperator(operatorType);
    }

    /**
     * 格式化SQL语句
     * 
     * @param sql 待格式化的SQL语句
     * @param indent 缩进层数
     * @param version 数据库版本
     * @return 已格式化的SQL语句
     */
    public String formatSql(String sql, int indent, DbVersion version) {
        SqlFormatter formatter = plugins.getSqlFormatter();
        return formatter.format(sql, indent, version);
    }

    /**
     * 替换SQL语句
     *
     * @param sql 源SQL语句
     * @param version 数据库版本
     * @return 替换后的SQL语句
     */
    public String replaceSql(String sql, DbVersion version) {
        SqlReplacer replacer = plugins.getSqlReplacer();
        return replacer.replace(sql, version);
    }

    /**
     * 查找数据库版本信息
     * 
     * @param datasource 数据源
     * @return 数据库版本信息
     */
    public DbVersion findDbVersion(DataSource datasource) {
        DbVersionFinder finder = plugins.getDbVersionFinder();
        return finder.findDbVersion(datasource);
    }

    /** 获取SQL模板扫描处理类 **/
    public SqlFileScanner getSqlFileScanner() {
        return plugins.getSqlFileScanner();
    }

    /** 获取SQL片断的选项配置类 **/
    public SqlFragmentOptions getSqlFragmentOptions() {
        return plugins.getSqlFragmentOptions();
    }

    /** 获取字段值校验处理类 **/
    public ColumnValueValidator getColumnValueValidator() {
        return plugins.getColumnValueValidator();
    }

    /** 获取命名转换接口 **/
    public JdbcNamingConverter getNamingConverter() {
        return plugins.getNamingConverter();
    }

    /** 获取批量操作的省略策略配置 **/
    public OmitStrategy getOmitStrategyOfBatchExecutor() {
        return plugins.getOmitStrategyOfBatchSql();
    }

    /** 获取IN语句的省略策略配置 **/
    public OmitStrategy getOmitStrategyOfInSql() {
        return plugins.getOmitStrategyOfInSql();
    }

    /** 多表关联列信息的解析器 **/
    public TablesFieldColumnParser getTablesFieldColumnParser() {
        return plugins.getTablesFieldColumnParser();
    }

    /** 根据数据库类型获取批量新增处理类 **/
    public BatchInsertExecutor getBatchInsertExecutor(DbVersion version) {
        return plugins.getBatchInsertExecutor(version);
    }

    /** 根据数据库类型获取批量更新处理类 **/
    public BatchUpdateExecutor getBatchUpdateExecutor(DbVersion version) {
        return plugins.getBatchUpdateExecutor(version);
    }

    /** Entity的表名缓存 **/
    private final Map<Class<?>, String> entityTableNameCache = new HashMap<>();

    /**
     * 扫描表名信息(有缓存)
     * 
     * @param clazz 类名
     * @return 表名
     */
    public String parseTableName(Class<?> clazz) {
        VerifyTools.requireNonNull(clazz, "class");
        if (entityTableNameCache.containsKey(clazz)) {
            return entityTableNameCache.get(clazz);
        }

        TableInfoScans scans = plugins.getTableInfoScans();
        String tableName = scans.scanTableName(clazz);
        entityTableNameCache.put(clazz, tableName);
        return tableName;
    }

    /** Entity的主键缓存 **/
    private final Map<Class<?>, SimpleFieldColumn> entityPrimaryKeyCache = new HashMap<>();

    /**
     * 扫描获取主键(有缓存)
     * 
     * @param clazz 类名
     * @return 主键
     */
    public SimpleFieldColumn parsePrimaryKey(Class<?> clazz) {
        VerifyTools.requireNonNull(clazz, "class");
        if (entityPrimaryKeyCache.containsKey(clazz)) {
            return entityPrimaryKeyCache.get(clazz);
        }

        AllFieldColumn<SimpleFieldColumn> all = parseAllFieldColumns(clazz);
        SimpleFieldColumn pk = all.findPrimaryKey();
        entityPrimaryKeyCache.put(clazz, pk);
        return pk;
    }

    /** TableJoin的列名缓存 **/
    private final Map<String, AllFieldColumn<TablesFieldColumn>> joinColumnsCache = new HashMap<>();

    /**
     * 扫描获取字段名和数据库列名的映射表(有缓存)
     * 
     * @param tables 表关联对象
     * @return AllFieldColumn: fieldName - columnName
     */
    public AllFieldColumn<TablesFieldColumn> parseAllFieldColumns(TableJoin tables) {
        VerifyTools.requireNonNull(tables, "tables");
        String cacheKey = tables.getTableFeatures(true);
        if (joinColumnsCache.containsKey(cacheKey)) {
            return joinColumnsCache.get(cacheKey);
        }
        TablesFieldColumnParser parser = plugins.getTablesFieldColumnParser();
        List<TablesFieldColumn> all = parser.parseFieldColumns(tables);
        AllFieldColumn<TablesFieldColumn> fieldColumns = new AllFieldColumn<>(all);
        joinColumnsCache.put(cacheKey, fieldColumns);
        return fieldColumns;
    }

    /** Entity的列名缓存 **/
    private final Map<Class<?>, AllFieldColumn<SimpleFieldColumn>> entityColumnsCache = new HashMap<>();

    /**
     * 扫描获取字段名和数据库列名的映射表(有缓存)
     * 
     * @param clazz 类型
     * @return AllFieldColumn: fieldName - columnName
     */
    public AllFieldColumn<SimpleFieldColumn> parseAllFieldColumns(Class<?> clazz) {
        VerifyTools.requireNonNull(clazz, "class");
        if (entityColumnsCache.containsKey(clazz)) {
            return entityColumnsCache.get(clazz);
        } else {
            TableInfoScans scans = plugins.getTableInfoScans();
            List<SimpleFieldColumn> fields = scans.scanColumnList(clazz);
            if (fields.isEmpty()) {
                String m = "Fields not found, please check config of TableInfoScans, class=" + clazz.getName();
                throw new IllegalArgumentException(m);
            }

            AllFieldColumn<SimpleFieldColumn> all = new AllFieldColumn<>(fields);
            entityColumnsCache.put(clazz, all);
            return all;
        }
    }
}
